msg_tool\scripts\artemis/
txt.rs

1use crate::scripts::base::*;
2use crate::types::*;
3use crate::utils::encoding::*;
4use crate::utils::escape::*;
5use anyhow::Result;
6use std::collections::HashSet;
7use std::ops::{Deref, DerefMut};
8use std::sync::Arc;
9use unicode_segmentation::UnicodeSegmentation;
10
11#[derive(Debug, Clone)]
12/// Artemis TXT script builder
13pub struct TxtBuilder {}
14
15impl TxtBuilder {
16    /// Creates a new instance of `TxtBuilder`
17    pub const fn new() -> Self {
18        Self {}
19    }
20}
21
22impl ScriptBuilder for TxtBuilder {
23    fn default_encoding(&self) -> Encoding {
24        Encoding::Utf8
25    }
26
27    fn build_script(
28        &self,
29        buf: Vec<u8>,
30        _filename: &str,
31        encoding: Encoding,
32        _archive_encoding: Encoding,
33        config: &ExtraConfig,
34        _archive: Option<&Box<dyn Script>>,
35    ) -> Result<Box<dyn Script>> {
36        Ok(Box::new(TxtScript::new(buf, encoding, config)?))
37    }
38
39    fn extensions(&self) -> &'static [&'static str] {
40        &["txt"]
41    }
42
43    fn script_type(&self) -> &'static ScriptType {
44        &ScriptType::ArtemisTxt
45    }
46}
47
48/// Artemis TXT script nodes
49pub trait Node {
50    /// Serialize the node to a string representation.
51    fn serialize(&self) -> String;
52}
53
54#[derive(Debug, Clone, PartialEq)]
55/// Represents a comment node in Artemis TXT scripts.
56pub struct CommentNode(pub String);
57
58impl Node for CommentNode {
59    fn serialize(&self) -> String {
60        format!("//{}", self.0)
61    }
62}
63
64#[derive(Clone, Debug, PartialEq)]
65/// Empty Line Node
66pub struct EmptyLineNode;
67
68impl Node for EmptyLineNode {
69    fn serialize(&self) -> String {
70        String::new()
71    }
72}
73
74#[derive(Debug, Clone, PartialEq)]
75/// Represents a label node in Artemis TXT scripts.
76pub struct LabelNode(pub String);
77
78impl Node for LabelNode {
79    fn serialize(&self) -> String {
80        format!("*{}", self.0)
81    }
82}
83
84#[derive(Debug, Clone, PartialEq)]
85/// Represents a tag node in Artemis TXT scripts.
86pub struct TagNode {
87    /// The name of the tag.
88    pub name: String,
89    /// The attributes of the tag, represented as a vector of key-value pairs.
90    pub attributes: Vec<(String, Option<String>)>,
91}
92
93impl Node for TagNode {
94    fn serialize(&self) -> String {
95        let attributes = self
96            .attributes
97            .iter()
98            .map(|(key, value)| {
99                if let Some(val) = value {
100                    format!("{}=\"{}\"", key, val)
101                } else {
102                    key.clone()
103                }
104            })
105            .collect::<Vec<_>>()
106            .join(" ");
107        if attributes.is_empty() {
108            format!("[{}]", self.name)
109        } else {
110            format!("[{} {}]", self.name, attributes)
111        }
112    }
113}
114
115impl TagNode {
116    fn ser_attributes_xml(&self) -> String {
117        let mut parts = Vec::new();
118        for (key, value) in self.attributes.iter() {
119            match value {
120                None => {
121                    parts.push(key.clone());
122                }
123                Some(val) => {
124                    parts.push(format!("{}=\"{}\"", key, escape_xml_attr_value(val)));
125                }
126            }
127        }
128        parts.join(" ")
129    }
130
131    /// Get attribute value of attribute in tag by name.
132    pub fn get_attr(&self, attr: &str) -> Option<&str> {
133        self.attributes
134            .iter()
135            .find(|(key, _)| key == attr)
136            .and_then(|(_, value)| value.as_deref())
137    }
138
139    /// Returns true if the tag is not suitable for name.
140    pub fn is_blocked_name(&self, set: &HashSet<String>) -> bool {
141        set.contains(&self.name)
142    }
143
144    /// Checks if the tag has a specific attribute.
145    pub fn has_attr(&self, attr: &str) -> bool {
146        self.attributes.iter().any(|(key, _)| key == attr)
147    }
148
149    /// Sets the value of an attribute in the tag.
150    pub fn set_attr(&mut self, attr: &str, value: Option<String>) {
151        if let Some(pos) = self.attributes.iter().position(|(key, _)| key == attr) {
152            self.attributes[pos].1 = value;
153        } else {
154            self.attributes.push((attr.to_string(), value));
155        }
156    }
157
158    /// Converts the node to a xml-like string representation.
159    pub fn to_xml(&self) -> String {
160        let attributes = self.ser_attributes_xml();
161        if attributes.is_empty() {
162            format!("<{}>", self.name)
163        } else {
164            format!("<{} {}>", self.name, attributes)
165        }
166    }
167}
168
169#[derive(Debug, Clone, PartialEq)]
170/// Represents a text node in Artemis TXT scripts.
171pub struct TextNode(pub String);
172
173#[derive(Debug, Clone, PartialEq)]
174/// Represents a node in a TXT line.
175pub enum TxtLineNode {
176    Comment(CommentNode),
177    Tag(TagNode),
178    Text(TextNode),
179}
180
181impl TxtLineNode {
182    /// Checks if the node is a tag.
183    ///
184    /// * `tag` - The name of the tag.
185    pub fn is_tag(&self, tag: &str) -> bool {
186        matches!(self, TxtLineNode::Tag(node) if node.name == tag)
187    }
188
189    /// Returns true if the tag is a blocked name.
190    pub fn is_tag_blocked_name(&self, set: &HashSet<String>) -> bool {
191        if let TxtLineNode::Tag(node) = self {
192            node.is_blocked_name(set)
193        } else {
194            false
195        }
196    }
197
198    /// Returns an iterator over the keys of the attributes of the tag node.
199    pub fn tag_attr_keys<'a>(&'a self) -> Box<dyn Iterator<Item = &'a str> + 'a> {
200        if let TxtLineNode::Tag(node) = self {
201            Box::new(node.attributes.iter().map(|(key, _)| key.as_str()))
202        } else {
203            Box::new(std::iter::empty())
204        }
205    }
206
207    pub fn tag_get_attr<'a>(&'a self, attr: &str) -> Option<&'a str> {
208        if let TxtLineNode::Tag(node) = self {
209            node.get_attr(attr)
210        } else {
211            None
212        }
213    }
214
215    /// Checks if the tag has a specific attribute.
216    pub fn tag_has_attr(&self, attr: &str) -> bool {
217        if let TxtLineNode::Tag(node) = self {
218            node.attributes.iter().any(|(key, _)| key == attr)
219        } else {
220            false
221        }
222    }
223
224    pub fn tag_set_attr(&mut self, attr: &str, value: Option<String>) {
225        if let TxtLineNode::Tag(node) = self {
226            node.set_attr(attr, value);
227        }
228    }
229
230    /// Converts the node to a xml-like string representation.
231    pub fn to_xml(&self) -> String {
232        match self {
233            TxtLineNode::Comment(_) => String::new(), // Ignore comments in XML
234            TxtLineNode::Tag(n) => {
235                if (n.name == "rt2" || n.name == "ret2") && n.attributes.is_empty() {
236                    "\n".to_string()
237                } else {
238                    n.to_xml()
239                }
240            }
241            TxtLineNode::Text(n) => escape_xml_text_value(&n.0),
242        }
243    }
244}
245
246impl Node for TxtLineNode {
247    fn serialize(&self) -> String {
248        match self {
249            TxtLineNode::Comment(node) => node.serialize(),
250            TxtLineNode::Tag(node) => node.serialize(),
251            TxtLineNode::Text(node) => node.0.clone(),
252        }
253    }
254}
255
256#[derive(Debug, Clone, PartialEq)]
257/// Represents a line in Artemis TXT scripts, which can contain multiple nodes.
258pub struct TxtLine(pub Vec<TxtLineNode>);
259
260impl Deref for TxtLine {
261    type Target = Vec<TxtLineNode>;
262
263    fn deref(&self) -> &Self::Target {
264        &self.0
265    }
266}
267
268impl DerefMut for TxtLine {
269    fn deref_mut(&mut self) -> &mut Self::Target {
270        &mut self.0
271    }
272}
273
274impl Node for TxtLine {
275    fn serialize(&self) -> String {
276        self.0
277            .iter()
278            .map(|node| node.serialize())
279            .collect::<Vec<_>>()
280            .join("")
281    }
282}
283
284impl TxtLine {
285    /// Converts the line to a xml-like string representation.
286    pub fn to_xml(&self) -> String {
287        self.0
288            .iter()
289            .map(|node| node.to_xml())
290            .collect::<Vec<_>>()
291            .join("")
292    }
293}
294
295#[derive(Debug, Clone, PartialEq)]
296/// Represents a parsed line in Artemis TXT scripts.
297pub enum ParsedLine {
298    /// Empty line.
299    Empty(EmptyLineNode),
300    /// Comment line.
301    Comment(CommentNode),
302    /// Label line.
303    Label(LabelNode),
304    /// Line
305    Line(TxtLine),
306}
307
308impl Node for ParsedLine {
309    fn serialize(&self) -> String {
310        match self {
311            ParsedLine::Empty(node) => node.serialize(),
312            ParsedLine::Comment(node) => node.serialize(),
313            ParsedLine::Label(node) => node.serialize(),
314            ParsedLine::Line(line) => line.serialize(),
315        }
316    }
317}
318
319impl ParsedLine {
320    /// Returns the length of the line.
321    pub fn len(&self) -> usize {
322        match self {
323            ParsedLine::Empty(_) => 0,
324            ParsedLine::Comment(_) => 0,
325            ParsedLine::Label(_) => 0,
326            ParsedLine::Line(line) => line.len(),
327        }
328    }
329
330    /// Push a node to the line.
331    pub fn push(&mut self, node: TxtLineNode) {
332        if let ParsedLine::Line(line) = self {
333            line.push(node);
334        } else {
335            // Do'nt care about other types
336        }
337    }
338
339    /// Inserts a node at the specified index in the line.
340    pub fn insert(&mut self, index: usize, node: TxtLineNode) {
341        if let ParsedLine::Line(line) = self {
342            line.insert(index, node);
343        } else {
344            // Do'nt care about other types
345        }
346    }
347
348    /// Remove a node at the specified index from the line.
349    pub fn remove(&mut self, index: usize) -> Option<TxtLineNode> {
350        if let ParsedLine::Line(line) = self {
351            if index < line.len() {
352                Some(line.remove(index))
353            } else {
354                None
355            }
356        } else {
357            // Do'nt care about other types
358            None
359        }
360    }
361}
362
363#[derive(Debug, Clone)]
364/// Represents a parsed Artemis TXT script.
365pub struct ParsedScript(pub Vec<ParsedLine>);
366
367impl Deref for ParsedScript {
368    type Target = Vec<ParsedLine>;
369
370    fn deref(&self) -> &Self::Target {
371        &self.0
372    }
373}
374
375impl DerefMut for ParsedScript {
376    fn deref_mut(&mut self) -> &mut Self::Target {
377        &mut self.0
378    }
379}
380
381impl Node for ParsedScript {
382    fn serialize(&self) -> String {
383        self.0
384            .iter()
385            .map(|line| line.serialize())
386            .collect::<Vec<_>>()
387            .join("\n")
388    }
389}
390
391/// Parser for Artemis TXT scripts.
392pub struct Parser {
393    lines: Vec<String>,
394}
395
396impl Parser {
397    pub fn new<S: AsRef<str> + ?Sized>(script: &S) -> Self {
398        let lines = script.as_ref().lines().map(|s| s.to_string()).collect();
399        Self { lines }
400    }
401
402    pub fn parse(&self, preserve_empty_lines: bool) -> Result<ParsedScript> {
403        let mut parsed_script = Vec::new();
404        let mut i = 0;
405        let line_count = self.lines.len();
406        while i < line_count {
407            let line = self.lines[i].trim();
408            i += 1;
409            if line.is_empty() {
410                if preserve_empty_lines {
411                    parsed_script.push(ParsedLine::Empty(EmptyLineNode));
412                }
413                continue;
414            }
415            if line.starts_with("//") {
416                parsed_script.push(ParsedLine::Comment(CommentNode(line[2..].to_string())));
417                continue;
418            }
419            if line.starts_with("*") {
420                let label = line[1..].trim().to_string();
421                parsed_script.push(ParsedLine::Label(LabelNode(label)));
422                continue;
423            }
424            let mut temp = String::new();
425            let mut nodes = Vec::new();
426            let mut line_graphs = line.graphemes(true).collect::<Vec<_>>();
427            let mut line_pos = 0;
428            let mut is_comment = false;
429            while line_pos < line_graphs.len() {
430                let graph = line_graphs[line_pos];
431                line_pos += 1;
432                temp.push_str(graph);
433                if is_comment {
434                    continue;
435                }
436                if !is_comment && temp.ends_with("//") && temp.len() > 2 {
437                    nodes.push(TxtLineNode::Text(TextNode(
438                        temp[..temp.len() - 2].to_string(),
439                    )));
440                    temp.clear();
441                    is_comment = true;
442                    continue;
443                }
444                if graph == "[" {
445                    if !temp.trim_end_matches("[").is_empty() {
446                        nodes.push(TxtLineNode::Text(TextNode(
447                            temp.trim_end_matches("[").to_string(),
448                        )));
449                    }
450                    // Tag may end in another line, so we need check it.
451                    while !line_graphs[line_pos..].contains(&"]") {
452                        if i < line_count {
453                            let nline = self.lines[i].trim();
454                            i += 1;
455                            // Add next line to the current line
456                            line_graphs.push("\n");
457                            line_graphs.extend(nline.graphemes(true));
458                        } else {
459                            break;
460                        }
461                    }
462                    let (tag, nextpos) = TagParser {
463                        graphs: &line_graphs,
464                        pos: line_pos,
465                    }
466                    .parse()?;
467                    line_pos = nextpos;
468                    nodes.push(TxtLineNode::Tag(tag));
469                    temp.clear();
470                    continue;
471                }
472            }
473            if is_comment {
474                nodes.push(TxtLineNode::Comment(CommentNode(temp)));
475            } else {
476                if !temp.is_empty() {
477                    nodes.push(TxtLineNode::Text(TextNode(temp)));
478                }
479            }
480            parsed_script.push(ParsedLine::Line(TxtLine(nodes)));
481        }
482        Ok(ParsedScript(parsed_script))
483    }
484}
485
486struct TagParser<'a> {
487    graphs: &'a [&'a str],
488    pos: usize,
489}
490
491impl<'a> TagParser<'a> {
492    fn peek(&self) -> Option<&'a str> {
493        self.graphs.get(self.pos).cloned()
494    }
495
496    fn eat(&mut self) {
497        if self.pos < self.graphs.len() {
498            self.pos += 1;
499        }
500    }
501
502    fn next(&mut self) -> Option<&'a str> {
503        if self.pos < self.graphs.len() {
504            let graph = self.graphs[self.pos];
505            self.pos += 1;
506            Some(graph)
507        } else {
508            None
509        }
510    }
511
512    fn is_indent(&self, indent: &str) -> bool {
513        let mut pos = self.pos;
514        for ident in indent.graphemes(true) {
515            if pos >= self.graphs.len() || self.graphs[pos] != ident {
516                return false;
517            }
518            pos += 1;
519        }
520        true
521    }
522
523    fn eat_all_equal(&mut self) {
524        while let Some(graph) = self.peek() {
525            if graph == "=" {
526                self.eat();
527            } else {
528                break;
529            }
530        }
531    }
532
533    fn parse(&mut self) -> Result<(TagNode, usize)> {
534        let name = self.parse_tag()?;
535        self.erase_whitespace();
536        let mut attributes = Vec::new();
537        loop {
538            let graph = match self.peek() {
539                Some(g) => g,
540                None => {
541                    return Err(anyhow::anyhow!(
542                        "Unexpected end of tag parsing: {}",
543                        self.graphs.join("")
544                    ));
545                }
546            };
547            if graph == "]" {
548                self.eat();
549                break;
550            }
551            if graph == " " || graph == "\t" {
552                self.eat();
553                continue;
554            }
555            if graph == "=" {
556                return Err(anyhow::anyhow!("Unexpected '=' without attribute name"));
557            }
558            let attr_name = self.parse_attr_name()?;
559            self.erase_whitespace();
560            let graph = match self.peek() {
561                Some(g) => g,
562                None => {
563                    return Err(anyhow::anyhow!(
564                        "Unexpected end of tag parsing: {}",
565                        self.graphs.join("")
566                    ));
567                }
568            };
569            if graph == "]" {
570                self.eat();
571                attributes.push((attr_name, None));
572                break;
573            }
574            if graph == "=" {
575                // Sometimes the script contains multiple equal signs
576                // We just ignore them
577                // Example: [イベントCG st = "拡大/ev005/a" add = "拡大/ev005/y2,拡大/ev005/r5" left = "min ~ max" top = "max ~ 1/4" mtime = "60000" ease == "減速" mfade = "1000" hide = "1"]
578                self.eat_all_equal();
579                self.erase_whitespace();
580                let value = self.parse_attr_value()?;
581                attributes.push((attr_name, Some(value)));
582                self.erase_whitespace();
583            } else {
584                attributes.push((attr_name, None));
585                self.erase_whitespace();
586                continue;
587            }
588        }
589        return Ok((TagNode { name, attributes }, self.pos));
590    }
591
592    fn erase_whitespace(&mut self) {
593        while let Some(graph) = self.peek() {
594            if graph == " " || graph == "\t" {
595                self.eat();
596            } else {
597                break;
598            }
599        }
600    }
601
602    fn parse_attr_name(&mut self) -> Result<String> {
603        let mut attr_name = String::new();
604        while let Some(graph) = self.peek() {
605            if graph == "=" || graph == " " || graph == "\t" || graph == "]" {
606                break;
607            }
608            attr_name.push_str(graph);
609            self.eat();
610        }
611        if attr_name.is_empty() {
612            return Err(anyhow::anyhow!("Empty attribute name found"));
613        }
614        Ok(attr_name)
615    }
616
617    fn parse_attr_value(&mut self) -> Result<String> {
618        let mut value = String::new();
619        if !self.is_indent("\"") {
620            return Err(anyhow::anyhow!(
621                "Expected attribute value to start with a quote: {}",
622                self.graphs.join("")
623            ));
624        }
625        self.eat(); // Skip the opening quote
626        while let Some(graph) = self.next() {
627            if graph == "\"" {
628                break; // End of attribute value
629            }
630            value.push_str(graph);
631        }
632        Ok(value)
633    }
634
635    fn parse_tag(&mut self) -> Result<String> {
636        let mut tag = String::new();
637        while let Some(graph) = self.peek() {
638            if graph == " " || graph == "\t" || graph == "]" {
639                break;
640            }
641            tag.push_str(graph);
642            self.eat();
643        }
644        if tag.is_empty() {
645            return Err(anyhow::anyhow!("Empty tag found"));
646        }
647        Ok(tag)
648    }
649}
650
651struct XMLTextParser<'a> {
652    str: &'a str,
653    lang: &'a str,
654    pos: usize,
655}
656
657impl<'a> XMLTextParser<'a> {
658    pub fn new(text: &'a str, lang: &'a str) -> Self {
659        Self {
660            str: text,
661            lang,
662            pos: 0,
663        }
664    }
665
666    fn parse_tag(&mut self) -> Result<TagNode> {
667        let mut name = String::new();
668        let mut attributes = Vec::new();
669        let mut is_name = true;
670        let mut is_key = false;
671        let mut is_value = false;
672        let mut is_in_quote = false;
673        let mut key = String::new();
674        let mut value = String::new();
675        while let Some(c) = self.next() {
676            match c {
677                '>' => {
678                    if !name.is_empty() {
679                        return Ok(TagNode { name, attributes });
680                    } else {
681                        return Err(anyhow::anyhow!("Empty tag name"));
682                    }
683                }
684                ' ' | '\t' => {
685                    if is_name {
686                        is_name = false;
687                        is_key = true;
688                    } else if is_key {
689                        if !key.is_empty() {
690                            attributes.push((key.clone(), None));
691                            key.clear();
692                        }
693                    } else if is_value {
694                        if is_in_quote {
695                            value.push(c);
696                        } else {
697                            if !value.is_empty() {
698                                attributes.push((key.clone(), Some(unescape_xml(&value))));
699                                key.clear();
700                                value.clear();
701                            }
702                            is_key = true;
703                            is_value = false;
704                        }
705                    }
706                }
707                '"' => {
708                    if is_in_quote {
709                        is_in_quote = false;
710                        if !value.is_empty() {
711                            attributes.push((key.clone(), Some(unescape_xml(&value))));
712                            key.clear();
713                            value.clear();
714                        }
715                        is_key = true;
716                    } else {
717                        is_in_quote = true;
718                    }
719                }
720                '=' => {
721                    if is_key {
722                        is_key = false;
723                        is_value = true;
724                    }
725                }
726                _ => {
727                    if is_name {
728                        name.push(c);
729                    } else if is_key {
730                        key.push(c);
731                    } else if is_value {
732                        value.push(c);
733                    } else {
734                        return Err(anyhow::anyhow!("Unexpected character in tag: {}", c));
735                    }
736                }
737            }
738        }
739        Err(anyhow::anyhow!("Unexpected end of input while parsing tag"))
740    }
741
742    pub fn parse(mut self) -> Result<Vec<ParsedLine>> {
743        let mut lines = Vec::new();
744        let mut current_line = Vec::new();
745        let mut text = String::new();
746        current_line.push(TxtLineNode::Tag(TagNode {
747            name: "lang".to_string(),
748            attributes: vec![(self.lang.to_string(), None)],
749        }));
750        while let Some(c) = self.next() {
751            match c {
752                '<' => {
753                    if !text.is_empty() {
754                        current_line.push(TxtLineNode::Text(TextNode(unescape_xml(&text))));
755                        text.clear();
756                    }
757                    let tag = self.parse_tag()?;
758                    let is_r = tag.name == "rt2" || tag.name == "ret2";
759                    current_line.push(TxtLineNode::Tag(tag));
760                    if is_r {
761                        lines.push(ParsedLine::Line(TxtLine(current_line)));
762                        current_line = Vec::new();
763                    }
764                }
765                '\n' => {
766                    if !text.is_empty() {
767                        current_line.push(TxtLineNode::Text(TextNode(unescape_xml(&text))));
768                        text.clear();
769                    }
770                    current_line.push(TxtLineNode::Tag(TagNode {
771                        name: "rt2".to_string(),
772                        attributes: Vec::new(),
773                    }));
774                    lines.push(ParsedLine::Line(TxtLine(current_line)));
775                    current_line = Vec::new();
776                }
777                _ => text.push(c),
778            }
779        }
780        if !text.is_empty() {
781            current_line.push(TxtLineNode::Text(TextNode(unescape_xml(&text))));
782        }
783        current_line.push(TxtLineNode::Tag(TagNode {
784            name: "/lang".to_string(),
785            attributes: Vec::new(),
786        }));
787        lines.push(ParsedLine::Line(TxtLine(current_line)));
788        Ok(lines)
789    }
790
791    fn next(&mut self) -> Option<char> {
792        if self.pos < self.str.len() {
793            let c = self.str[self.pos..].chars().next()?;
794            self.pos += c.len_utf8();
795            Some(c)
796        } else {
797            None
798        }
799    }
800}
801
802#[derive(Debug)]
803pub struct TxtScript {
804    tree: ParsedScript,
805    blacklist_names: Arc<HashSet<String>>,
806    lang: Option<String>,
807}
808
809impl TxtScript {
810    /// Creates a new instance of `TxtScript` from the given buffer and encoding.
811    pub fn new(buf: Vec<u8>, encoding: Encoding, config: &ExtraConfig) -> Result<Self> {
812        let script = decode_to_string(encoding, &buf, true)?;
813        let parser = Parser::new(&script);
814        let tree = parser.parse(true)?;
815        Ok(Self {
816            tree,
817            blacklist_names: config.artemis_txt_blacklist_names.clone(),
818            lang: config.artemis_txt_lang.clone(),
819        })
820    }
821}
822
823impl Script for TxtScript {
824    fn default_output_script_type(&self) -> OutputScriptType {
825        OutputScriptType::Json
826    }
827
828    fn default_format_type(&self) -> FormatOptions {
829        FormatOptions::None
830    }
831
832    fn extract_messages(&self) -> Result<Vec<Message>> {
833        let mut messages = Vec::new();
834        let mut i = 0;
835        let len = self.tree.len();
836        let mut last_tag_block: Option<TagNode> = None;
837        let mut lang = self.lang.as_ref().map(|s| s.as_str());
838        let mut message = TxtLine(Vec::new());
839        let mut in_lang_block = false;
840        let mut droped_lang_block = false;
841        let mut is_selectblk = false;
842        while i < len {
843            let line = &self.tree[i];
844            if let ParsedLine::Line(line) = line {
845                for node in line.iter() {
846                    if node.is_tag("lang") {
847                        let lan = match lang {
848                            Some(l) => l,
849                            None => {
850                                for key in node.tag_attr_keys() {
851                                    lang = Some(key);
852                                    break;
853                                }
854                                match lang {
855                                    Some(l) => l,
856                                    None => {
857                                        return Err(anyhow::anyhow!(
858                                            "No language found in lang tag"
859                                        ));
860                                    }
861                                }
862                            }
863                        };
864                        if node.tag_has_attr(lan) {
865                            in_lang_block = true;
866                        } else {
867                            droped_lang_block = true;
868                        }
869                    } else if node.is_tag("/lang") {
870                        in_lang_block = false;
871                        droped_lang_block = false;
872                    } else if node.is_tag("printlang") {
873                        let mes = message.to_xml();
874                        message.clear();
875                        if !mes.is_empty() {
876                            let name = if mes.starts_with("「") {
877                                match &last_tag_block {
878                                    Some(block) => {
879                                        Some(if let Some(name) = block.get_attr("name") {
880                                            name.to_string()
881                                        } else {
882                                            block.name.clone()
883                                        })
884                                    }
885                                    _ => {
886                                        eprintln!("Warn: Name block not found.");
887                                        crate::COUNTER.inc_warning();
888                                        None
889                                    }
890                                }
891                            } else {
892                                None
893                            };
894                            messages.push(Message { name, message: mes });
895                        }
896                        last_tag_block = None;
897                    } else if node.is_tag("selectbtn_init") {
898                        is_selectblk = true;
899                    } else if node.is_tag("selectbtn") {
900                        let mut lan = match lang {
901                            Some(l) => l,
902                            None => {
903                                for key in node.tag_attr_keys() {
904                                    if key == "label" || key == "call" {
905                                        continue;
906                                    }
907                                    lang = Some(key);
908                                    break;
909                                }
910                                match lang {
911                                    Some(l) => l,
912                                    None => {
913                                        return Err(anyhow::anyhow!(
914                                            "No language found in selectbtn tag"
915                                        ));
916                                    }
917                                }
918                            }
919                        };
920                        if !node.tag_has_attr(lan) {
921                            for key in node.tag_attr_keys() {
922                                if key == "label" || key == "call" {
923                                    continue;
924                                }
925                                lan = key;
926                                break;
927                            }
928                        }
929                        if let Some(t) = node.tag_get_attr(lan) {
930                            messages.push(Message {
931                                name: None,
932                                message: t.to_string(),
933                            });
934                        }
935                    } else if node.is_tag("/selectbtn") {
936                        is_selectblk = false;
937                    } else if in_lang_block {
938                        message.push(node.clone());
939                    } else if droped_lang_block {
940                        // Drop the message if we are in a dropped lang block
941                    } else if is_selectblk {
942                        // Drop other nodes in select block
943                    } else if let TxtLineNode::Tag(tag) = node {
944                        if !tag.is_blocked_name(&self.blacklist_names) {
945                            last_tag_block = Some(tag.clone());
946                        }
947                    }
948                }
949            }
950            i += 1;
951        }
952        Ok(messages)
953    }
954
955    fn import_messages<'a>(
956        &'a self,
957        messages: Vec<Message>,
958        mut file: Box<dyn WriteSeek + 'a>,
959        _filename: &str,
960        encoding: Encoding,
961        replacement: Option<&'a ReplacementTable>,
962    ) -> Result<()> {
963        let mut output = self.tree.clone();
964        let mut current_line = 0;
965        let mut last_tag_block_loc = None;
966        let mut lang = self.lang.clone();
967        let mut mes = messages.iter();
968        let mut mess = mes.next();
969        let mut lang_block_index = None;
970        let mut lang_end_block_index = None;
971        let mut in_lang_block = false;
972        let mut droped_lang_block = false;
973        let mut is_selectblk = false;
974        while current_line < output.len() {
975            let line = output[current_line].clone();
976            if let ParsedLine::Line(line) = &line {
977                for (i, node) in line.iter().enumerate() {
978                    if node.is_tag("lang") {
979                        let lan = match lang.as_ref() {
980                            Some(l) => l.as_str(),
981                            None => {
982                                for key in node.tag_attr_keys() {
983                                    lang = Some(key.to_string());
984                                    break;
985                                }
986                                match lang.as_ref() {
987                                    Some(l) => l.as_str(),
988                                    None => {
989                                        return Err(anyhow::anyhow!(
990                                            "No language found in lang tag"
991                                        ));
992                                    }
993                                }
994                            }
995                        };
996                        if node.tag_has_attr(lan) {
997                            in_lang_block = true;
998                            lang_block_index = Some((current_line, i));
999                        } else {
1000                            droped_lang_block = true;
1001                        }
1002                    } else if node.is_tag("/lang") {
1003                        if in_lang_block {
1004                            lang_end_block_index = Some((current_line, i));
1005                        }
1006                        in_lang_block = false;
1007                        droped_lang_block = false;
1008                    } else if node.is_tag("printlang") {
1009                        let lan = lang
1010                            .as_ref()
1011                            .map(|s| s.as_str())
1012                            .ok_or(anyhow::anyhow!("No language specified."))?;
1013                        let m = match mess {
1014                            Some(m) => m,
1015                            None => {
1016                                return Err(anyhow::anyhow!("Not enough messages."));
1017                            }
1018                        };
1019                        if let Some(name) = &m.name {
1020                            let block_index: (usize, usize) = match last_tag_block_loc.take() {
1021                                Some(data) => data,
1022                                None => {
1023                                    return Err(anyhow::anyhow!(
1024                                        "No name tag block found before printlang.",
1025                                    ));
1026                                }
1027                            };
1028                            let mut name = name.clone();
1029                            if let Some(repl) = replacement {
1030                                for (k, v) in &repl.map {
1031                                    name = name.replace(k, v);
1032                                }
1033                            }
1034                            let mblock = &mut output[block_index.0];
1035                            if let ParsedLine::Line(txt_line) = mblock {
1036                                let block = txt_line[block_index.1].clone();
1037                                if let TxtLineNode::Tag(mut tag) = block {
1038                                    tag.set_attr("name", Some(name));
1039                                    txt_line[block_index.1] = TxtLineNode::Tag(tag);
1040                                } else {
1041                                    return Err(anyhow::anyhow!(
1042                                        "Last tag block is not a tag: {:?}",
1043                                        mblock
1044                                    ));
1045                                }
1046                            } else {
1047                                return Err(anyhow::anyhow!(
1048                                    "Last tag block is not a line: {:?}",
1049                                    mblock
1050                                ));
1051                            }
1052                        }
1053                        let mut message = m.message.clone();
1054                        if let Some(repl) = replacement {
1055                            for (k, v) in &repl.map {
1056                                message = message.replace(k, v);
1057                            }
1058                        }
1059                        let mut nodes = XMLTextParser::new(&message, lan).parse()?;
1060                        if lang_block_index.is_some() && lang_end_block_index.is_some() {
1061                            let start_index = lang_block_index.unwrap();
1062                            let end_index = lang_end_block_index.unwrap();
1063                            if start_index.1 != 0 {
1064                                let block = output[start_index.0].clone();
1065                                if let ParsedLine::Line(txt_line) = block {
1066                                    for i in 0..start_index.1 {
1067                                        nodes[0].insert(i, txt_line[i].clone());
1068                                    }
1069                                } else {
1070                                    return Err(anyhow::anyhow!(
1071                                        "Lang block start is not a line: {:?}",
1072                                        block
1073                                    ));
1074                                }
1075                            }
1076                            if end_index.1 + 1 < output[end_index.0].len() {
1077                                let block = output[end_index.0].clone();
1078                                if let ParsedLine::Line(txt_line) = block {
1079                                    for i in end_index.1 + 1..txt_line.len() {
1080                                        nodes.last_mut().unwrap().push(txt_line[i].clone());
1081                                    }
1082                                } else {
1083                                    return Err(anyhow::anyhow!(
1084                                        "Lang block end is not a line: {:?}",
1085                                        block
1086                                    ));
1087                                }
1088                            }
1089                            let ori_len = (end_index.0 - start_index.0 + 1) as isize;
1090                            let new_len = nodes.len() as isize;
1091                            for _ in start_index.0..=end_index.0 {
1092                                output.remove(start_index.0);
1093                            }
1094                            let mut start_index = start_index.0;
1095                            for node in nodes {
1096                                output.insert(start_index, node);
1097                                start_index += 1;
1098                            }
1099                            current_line = (current_line as isize + new_len - ori_len) as usize;
1100                        } else {
1101                            // Add a new lang block if not exists
1102                            for node in nodes {
1103                                output.insert(current_line, node);
1104                                current_line += 1;
1105                            }
1106                        }
1107                        lang_block_index = None;
1108                        lang_end_block_index = None;
1109                        mess = mes.next();
1110                        last_tag_block_loc = None;
1111                    } else if node.is_tag("selectbtn_init") {
1112                        is_selectblk = true;
1113                    } else if node.is_tag("selectbtn") {
1114                        let lan = match lang.as_ref() {
1115                            Some(l) => l.as_str(),
1116                            None => {
1117                                for key in node.tag_attr_keys() {
1118                                    if key == "label" || key == "call" {
1119                                        continue;
1120                                    }
1121                                    lang = Some(key.to_string());
1122                                    break;
1123                                }
1124                                match lang.as_ref() {
1125                                    Some(l) => l.as_str(),
1126                                    None => {
1127                                        return Err(anyhow::anyhow!(
1128                                            "No language found in selectbtn tag"
1129                                        ));
1130                                    }
1131                                }
1132                            }
1133                        };
1134                        let m = match mess {
1135                            Some(m) => m,
1136                            None => {
1137                                return Err(anyhow::anyhow!("Not enough messages."));
1138                            }
1139                        };
1140                        let mut message = m.message.clone();
1141                        if let Some(repl) = replacement {
1142                            for (k, v) in &repl.map {
1143                                message = message.replace(k, v);
1144                            }
1145                        }
1146                        let mut node = node.clone();
1147                        node.tag_set_attr(lan, Some(message));
1148                        let block = &mut output[current_line];
1149                        if let ParsedLine::Line(txt_line) = block {
1150                            txt_line[i] = node;
1151                        } else {
1152                            return Err(anyhow::anyhow!("selectbtn tag not in line: {:?}", block));
1153                        }
1154                        mess = mes.next();
1155                    } else if node.is_tag("/selectbtn") {
1156                        is_selectblk = false;
1157                    } else if in_lang_block {
1158                        // Do nothing
1159                    } else if droped_lang_block {
1160                        // Drop the message if we are in a dropped lang block
1161                    } else if is_selectblk {
1162                        // Ignore other nodes in select block
1163                    } else if let TxtLineNode::Tag(tag) = node {
1164                        if !tag.is_blocked_name(&self.blacklist_names) {
1165                            last_tag_block_loc = Some((current_line, i));
1166                        }
1167                    }
1168                }
1169            }
1170            current_line += 1;
1171        }
1172        let s = output.serialize();
1173        let encoded = encode_string(encoding, &s, false)?;
1174        file.write_all(&encoded)?;
1175        file.flush()?;
1176        Ok(())
1177    }
1178}
1179
1180#[test]
1181fn test_xml_parser() {
1182    let data = "测试文本\nok<r a=\"b\">测试<b o=\"文本\n换行\">";
1183    let data = XMLTextParser::new(data, "en").parse().unwrap();
1184    assert_eq!(
1185        data,
1186        vec![
1187            ParsedLine::Line(TxtLine(vec![
1188                TxtLineNode::Tag(TagNode {
1189                    name: "lang".to_string(),
1190                    attributes: vec![("en".to_string(), None)],
1191                }),
1192                TxtLineNode::Text(TextNode("测试文本".to_string())),
1193                TxtLineNode::Tag(TagNode {
1194                    name: "rt2".to_string(),
1195                    attributes: vec![],
1196                }),
1197            ])),
1198            ParsedLine::Line(TxtLine(vec![
1199                TxtLineNode::Text(TextNode("ok".to_string())),
1200                TxtLineNode::Tag(TagNode {
1201                    name: "r".to_string(),
1202                    attributes: vec![("a".to_string(), Some("b".to_string()))],
1203                }),
1204                TxtLineNode::Text(TextNode("测试".to_string())),
1205                TxtLineNode::Tag(TagNode {
1206                    name: "b".to_string(),
1207                    attributes: vec![("o".to_string(), Some("文本\n换行".to_string()))],
1208                }),
1209                TxtLineNode::Tag(TagNode {
1210                    name: "/lang".to_string(),
1211                    attributes: vec![],
1212                }),
1213            ])),
1214        ],
1215    );
1216}